﻿using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Media3D;
using LFSRecord2.Misc;
using LFSRecord2.View.TimelineStructures;

namespace LFSRecord2.Model.ProjectStorage
{
    public static class ProjectStorageV2
    {
        public static void Save(LfsRecordProject project, FileStream fs, BinaryWriter bfs)
        {
            bfs.Write(FileHelper.StringToByteArray("LFSRecord\0"));
            bfs.Write((ushort)2);
            bfs.Write(FileHelper.StringToByteArray(project.ProjectName.PadRight(22, Char.MinValue)));

            bfs.Write((int)0);
            bfs.Write((int)0);
            bfs.Write(Char.MinValue);
            bfs.Write(Char.MinValue);

            bfs.Write(project.ProjectLength);

            bfs.Write(project.FPS);
            bfs.Write(Char.MinValue);
            bfs.Write(Char.MinValue);
            bfs.Write(Char.MinValue);

            bfs.Write(FileHelper.StringToByteArray(project.InSimIP.PadRight(16, Char.MinValue)));
            bfs.Write(project.InSimPort);
            bfs.Write(FileHelper.StringToByteArray(project.InSimAdminPassword.PadRight(16, Char.MinValue)));

            byte flags = 0;
            if (project.RecordFullScreenOverride)
                flags += 1;
            if (project.RecordHide2D)
                flags += 2;
            if (project.Move3DAxesInvert)
                flags += 4;
            bfs.Write(flags);
            bfs.Write(project.RecordStopHotkey);

            bfs.Write((byte)project.Layers.Count);
            bfs.Write(Char.MinValue);
            bfs.Write(Char.MinValue);
            bfs.Write(Char.MinValue);

            bfs.Write(project.RecordingWidth);
            bfs.Write(project.RecordingHeight);

            for (int a = 0; a < 156; a++)
                bfs.Write(Char.MinValue);

            // Layers
            foreach (CompLayerBase layer in project.Layers)
            {
                Debug.WriteLine("Storing Layer : " + layer.LayerName);

                bfs.Write(FileHelper.StringToByteArray(layer.LayerName.PadRight(24, Char.MinValue)));
                bfs.Write((byte)layer.LayerType);

                flags = 0;
                if (layer.IsCollapsed)
                    flags += 1;
                if (layer.IsSelected)
                    flags += 2;
                if (layer.LayerType == CompLayerTypes.LookCamera)
                {
                    if (((CompLayerLookCamera)layer).IsActiveCamera)
                        flags += 4;
                }
                else if (layer.LayerType == CompLayerTypes.FreeCamera)
                {
                    if (((CompLayerFreeCamera)layer).IsActiveCamera)
                        flags += 4;
                }
                bfs.Write(flags);
                bfs.Write((byte)layer.Properties.Count);

                bfs.Write((byte)((layer.HasParentLayer) ? layer.ParentLayer.LayerIndex + 1 : 0));

                // spares
                bfs.Write((uint)0);
                bfs.Write((uint)0);
                bfs.Write((uint)0);
                bfs.Write((uint)0);

                // Layer properties
                foreach (CompLayerPropertyBase prop in layer.Properties)
                {
                    bfs.Write((byte)prop.PropertyValueType);
                    flags = 0;
                    if (prop.IsSelected)
                        flags += 1;
                    if (prop.IsAnimated)
                        flags += 2;
                    if (prop.ValueLock)
                        flags += 4;
                    if (prop.BoundPropertyIgnoreParent)
                        flags += 128;
                    //Debug.WriteLine(prop.PropertyName + " - flags : {0}", flags);
                    bfs.Write(flags);
                    bfs.Write((ushort)prop.KeyFrames.Count);

                    bfs.Write(prop.BindingPropertyIndex.LayerIndex);
                    bfs.Write(prop.BindingPropertyIndex.PropertyIndex);
                    bfs.Write(Char.MinValue);

                    if (prop.PropertyValueType == LayerPropertyValueTypes.Point3D)
                    {
                        bfs.Write(((CompLayerPropertyPoint3D)prop).CurrentXValue);
                        bfs.Write(((CompLayerPropertyPoint3D)prop).CurrentYValue);
                        bfs.Write(((CompLayerPropertyPoint3D)prop).CurrentZValue);

                        foreach (CustomPoint3DKeyFrame kf in prop.KeyFrames)
                        {
                            bfs.Write(kf.Value.X); bfs.Write(kf.Value.Y); bfs.Write(kf.Value.Z);

                            bfs.Write(kf.FrameNum);

                            bfs.Write(kf.EaseInValue); bfs.Write(kf.EaseOutValue);

                            bfs.Write((byte)kf.InterpolationTypeIn);
                            bfs.Write((byte)kf.InterpolationTypeOut);

                            bfs.Write((byte)kf.Options);
                            bfs.Write((byte)(kf.IsSelected ? 1 : 0));

                            bfs.Write(kf.BezierControlIn.X); bfs.Write(kf.BezierControlIn.Y); bfs.Write(kf.BezierControlIn.Z);
                            bfs.Write(kf.BezierControlOut.X); bfs.Write(kf.BezierControlOut.Y); bfs.Write(kf.BezierControlOut.Z);

                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);

                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                        }
                    }
                    else if (prop.PropertyValueType == LayerPropertyValueTypes.Double)
                    {
                        bfs.Write(((CompLayerPropertyDouble)prop).CurrentValue);

                        foreach (CustomDoubleKeyFrame kf in prop.KeyFrames)
                        {
                            bfs.Write(kf.Value);

                            bfs.Write(kf.FrameNum);

                            bfs.Write(kf.EaseInValue); bfs.Write(kf.EaseOutValue);

                            bfs.Write((byte)kf.InterpolationTypeIn);
                            bfs.Write((byte)kf.InterpolationTypeOut);

                            bfs.Write((byte)kf.Options);
                            bfs.Write((byte)(kf.IsSelected ? 1 : 0));

                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);

                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                        }
                    }
                    else if (prop.PropertyValueType == LayerPropertyValueTypes.String)
                    {
                        bfs.Write(FileHelper.StringToByteArray(((CompLayerPropertyString)prop).CurrentValue.PadRight(64, Char.MinValue)));

                        foreach (CustomStringKeyFrame kf in prop.KeyFrames)
                        {
                            bfs.Write(FileHelper.StringToByteArray(kf.Value.PadRight(64, Char.MinValue)));

                            bfs.Write(kf.FrameNum);

                            bfs.Write((byte)kf.Options);
                            bfs.Write((byte)(kf.IsSelected ? 1 : 0));

                            bfs.Write((byte)0);
                            bfs.Write((byte)0);
                        }
                    }
                }
            }
        }

        public static void Load(LfsRecordProject project, FileStream fs, BinaryReader bfs)
        {
            // Offset 34
            bfs.ReadUInt64();
            bfs.ReadByte();
            bfs.ReadByte();

            project.ProjectLength = bfs.ReadDouble();

            project.FPS = bfs.ReadByte();
            bfs.ReadByte();
            bfs.ReadByte();
            bfs.ReadByte();

            project.InSimIP = FileHelper.CharArrayToString(bfs.ReadChars(16));
            project.InSimPort = bfs.ReadUInt16();
            project.InSimAdminPassword = FileHelper.CharArrayToString(bfs.ReadChars(16));

            byte projectFlags = bfs.ReadByte();
            project.RecordFullScreenOverride = (projectFlags & 1) > 0;
            project.RecordHide2D = (projectFlags & 2) > 0;
            project.Move3DAxesInvert = (projectFlags & 4) > 0;
            project.RecordStopHotkey = bfs.ReadByte();
            if (project.RecordStopHotkey == 0)
                project.RecordStopHotkey = 106;     // default to * if 0

            byte numLayers = bfs.ReadByte();
            bfs.ReadByte();
            bfs.ReadByte();
            bfs.ReadByte();
            LayerInfoObject[] layersInfo = new LayerInfoObject[numLayers];

            project.RecordingWidth = bfs.ReadUInt16();
            project.RecordingHeight = bfs.ReadUInt16();

            fs.Position = 256;

            for (int a = 0; a < numLayers; a++)
            {
                // Check if there's enough data available for layer header
                if (fs.Position + 36 > fs.Length)
                {
                    throw new ProjectLoadException("Error loading project :\n\nFile appears to be missing data");
                }

                string layerName = FileHelper.CharArrayToString(bfs.ReadChars(24));
                CompLayerTypes layerType = (CompLayerTypes)bfs.ReadByte();
                int layerFlags = bfs.ReadByte();
                int numProperties = bfs.ReadByte();
                int parentLayerIndex = (int)bfs.ReadByte() - 1;

                bfs.ReadUInt32();
                bfs.ReadUInt32();
                bfs.ReadUInt32();
                bfs.ReadUInt32();

                layersInfo[a] = new LayerInfoObject(a, -1, layerType, layerName, numProperties, parentLayerIndex);

                if (layerType == CompLayerTypes.LookCamera)
                {
                    project.AddLookCamera(layerName);
                }
                else if (layerType == CompLayerTypes.FreeCamera)
                {
                    project.AddFreeCamera(layerName);
                }
                else if (layerType == CompLayerTypes.Null)
                {
                    project.AddNull(layerName);
                }
                else if (layerType == CompLayerTypes.ReplayControl)
                {
                    project.AddReplayControl(layerName);
                }
                else
                {
                    throw new ProjectLoadException("Error loading project :\n\nInvalid layerType encountered");
                }

                CompLayerBase layer = project.Layers[a];
                layer.IsCollapsed = (layerFlags & 1) > 0;
                layer.IsSelected = (layerFlags & 2) > 0;
                if (layerType == CompLayerTypes.LookCamera)
                {
                    ((CompLayerLookCamera)layer).IsActiveCamera = (layerFlags & 4) > 0;
                }
                else if (layerType == CompLayerTypes.FreeCamera)
                {
                    ((CompLayerFreeCamera)layer).IsActiveCamera = (layerFlags & 4) > 0;
                }

                for (int b = 0; b < numProperties; b++)
                {
                    // Check if there's enough data available for this block
                    if (fs.Position + 8 > fs.Length)
                    {
                        throw new ProjectLoadException("Error loading project :\n\nFile appears to be missing data");
                    }

                    CompLayerPropertyBase prop = layer.Properties[b];
                    prop.Initializing = true;

                    LayerPropertyValueTypes propertyValueType = (LayerPropertyValueTypes)bfs.ReadByte();
                    int propFlags = bfs.ReadByte();
                    int numKeyFrames = bfs.ReadUInt16();
                    prop.HasSelectedKeyFrames = numKeyFrames > 0;

                    layersInfo[a].Properties[b] = new PropertyInfoObject(b, propertyValueType, "");
                    layersInfo[a].Properties[b].BindingPropIndex.LayerIndex = bfs.ReadInt16();
                    layersInfo[a].Properties[b].BindingPropIndex.PropertyIndex = bfs.ReadSByte();
                    bfs.ReadByte();

                    if (propertyValueType == LayerPropertyValueTypes.Point3D)
                    {
                        // Check if there's enough data available for this block
                        if (fs.Position + 24 + 104 * numKeyFrames > fs.Length)
                        {
                            throw new ProjectLoadException("Error loading project :\n\nFile appears to be missing data");
                        }

                        ((CompLayerPropertyPoint3D)prop).CurrentXValue = bfs.ReadDouble();
                        ((CompLayerPropertyPoint3D)prop).CurrentYValue = bfs.ReadDouble();
                        ((CompLayerPropertyPoint3D)prop).CurrentZValue = bfs.ReadDouble();

                        for (int c = 0; c < numKeyFrames; c++)
                        {
                            Point3D p = new Point3D(bfs.ReadDouble(), bfs.ReadDouble(), bfs.ReadDouble());
                            int frameNum = bfs.ReadInt32();

                            CustomPoint3DKeyFrame kfp = new CustomPoint3DKeyFrame(frameNum, p);
                            kfp.ParentProperty = prop;
                            kfp.ParentLayer = layer;

                            kfp.EaseInValue = bfs.ReadDouble();
                            kfp.EaseOutValue = bfs.ReadDouble();

                            kfp.InterpolationTypeIn = (KeyFrameInterpolationTypes)bfs.ReadByte();
                            kfp.InterpolationTypeOut = (KeyFrameInterpolationTypes)bfs.ReadByte();

                            kfp.Options = (CustomKeyFrameOptions)bfs.ReadByte();
                            int frameFlags = bfs.ReadByte();
                            //kfp.IsSelected = false;
                            kfp.IsSelected = (frameFlags & 1) > 0;
                            if (kfp.IsSelected)
                                prop.IsSelected = true;

                            //Debug.WriteLine("AutoBezier Load Options : " + kfp.Options);

                            kfp.BezierControlIn = new Point3D(bfs.ReadDouble(), bfs.ReadDouble(), bfs.ReadDouble());
                            kfp.BezierControlOut = new Point3D(bfs.ReadDouble(), bfs.ReadDouble(), bfs.ReadDouble());

                            bfs.ReadUInt32();
                            bfs.ReadUInt32();

                            ((CompLayerPropertyPoint3D)prop).AddKeyFrame(kfp, true);
                        }
                    }
                    else if (propertyValueType == LayerPropertyValueTypes.Double)
                    {
                        // Check if there's enough data available for this block
                        if (fs.Position + 8 + 40 * numKeyFrames > fs.Length)
                        {
                            throw new ProjectLoadException("Error loading project :\n\nFile appears to be missing data");
                        }

                        ((CompLayerPropertyDouble)prop).CurrentValue = bfs.ReadDouble();

                        for (int c = 0; c < numKeyFrames; c++)
                        {
                            double keyValue = bfs.ReadDouble();
                            int frameNum = bfs.ReadInt32();

                            CustomDoubleKeyFrame kfd = new CustomDoubleKeyFrame(frameNum, keyValue);

                            kfd.EaseInValue = bfs.ReadDouble();
                            kfd.EaseOutValue = bfs.ReadDouble();

                            kfd.InterpolationTypeIn = (KeyFrameInterpolationTypes)bfs.ReadByte();
                            kfd.InterpolationTypeOut = (KeyFrameInterpolationTypes)bfs.ReadByte();

                            kfd.Options = (CustomKeyFrameOptions)bfs.ReadByte();
                            int frameFlags = bfs.ReadByte();
                            kfd.IsSelected = (frameFlags & 1) > 0;
                            if (kfd.IsSelected)
                                prop.IsSelected = true;

                            bfs.ReadUInt32();
                            bfs.ReadUInt32();

                            ((CompLayerPropertyDouble)prop).AddKeyFrame(kfd);
                        }
                    }
                    else if (propertyValueType == LayerPropertyValueTypes.String)
                    {
                        // Check if there's enough data available for this block
                        if (fs.Position + 8 + 72 * numKeyFrames > fs.Length)
                        {
                            throw new ProjectLoadException("Error loading project :\n\nFile appears to be missing data");
                        }

                        ((CompLayerPropertyString)prop).CurrentValue = FileHelper.CharArrayToString(bfs.ReadChars(64));

                        for (int c = 0; c < numKeyFrames; c++)
                        {
                            string keyValueString = FileHelper.CharArrayToString(bfs.ReadChars(64));
                            int frameNum = bfs.ReadInt32();

                            CustomStringKeyFrame kfs = new CustomStringKeyFrame(frameNum, keyValueString);

                            kfs.Options = (CustomKeyFrameOptions)bfs.ReadByte();
                            int frameFlags = bfs.ReadByte();
                            kfs.IsSelected = (frameFlags & 1) > 0;

                            bfs.ReadByte();
                            bfs.ReadByte();

                            ((CompLayerPropertyString)prop).AddKeyFrame(kfs);
                        }
                    }
                    else
                    {
                        // ERROR
                        throw new ProjectLoadException("Error loading project :\n\nInvalid layerPropertyValueType encountered");
                    }

                    if (!prop.IsSelected)
                        prop.IsSelected = (propFlags & 1) > 0;
                    if (prop.IsSelected)
                        layer.IsSelected = true;

                    if (numKeyFrames > 0)
                        prop.IsAnimated = (propFlags & 2) > 0;
                    if (prop.HasValueLock)
                        prop.ValueLock = (propFlags & 4) > 0;
                    prop.BoundPropertyIgnoreParent = (propFlags & 128) > 0;

                    prop.Initializing = false;

                    //layer.UpdatedCurrentValues += (uint)Math.Pow(2, b);
                }

                // Final layer settings
            }

            // Now we must loop through the LayerInfo array to check for property bindings
            bool ignore = false;
            for (int a = 0; a < layersInfo.Length; a++)
            {
                for (int b = 0; b < layersInfo[a].NumProperties; b++)
                {
                    if (layersInfo[a].Properties[b].BindingPropIndex.LayerIndex > -1 && layersInfo[a].Properties[b].BindingPropIndex.PropertyIndex > -1)
                    {
                        //Debug.WriteLine("Setting binding for layer {0} prop {1}, on layer {2} prop {3}", a, b, layersInfo[a].Properties[b].BindingPropIndex.LayerIndex, layersInfo[a].Properties[b].BindingPropIndex.PropertyIndex);

                        // Set prop binding
                        ignore = project.Layers[a].Properties[b].BoundPropertyIgnoreParent;
                        project.Layers[a].Properties[b].BoundProperty = project.GetProperty(layersInfo[a].Properties[b].BindingPropIndex);
                        project.Layers[a].Properties[b].BoundPropertyIgnoreParent = ignore;
                    }
                }

                if (layersInfo[a].ParentLayerIndex > -1)
                {
                    project.Layers[a].ParentLayer = project.Layers[layersInfo[a].ParentLayerIndex];
                }
            }

            //Debug.WriteLine("File pointer : {0} - file size : {1}", fs.Position, fs.Length);
            if (fs.Position != fs.Length)
            {
                // We could decide to still cancel the project load here
            }
        }
    }
}
